package com.reuters.rfa.example.omm.domainServer.marketbyorder;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;

import com.reuters.rfa.common.Token;
import com.reuters.rfa.example.framework.prov.ProvDomainMgr;
import com.reuters.rfa.example.omm.domainServer.DataGenerator;
import com.reuters.rfa.example.omm.domainServer.DataStreamItem;
import com.reuters.rfa.example.utility.CommandLine;
import com.reuters.rfa.omm.OMMMapEntry;
import com.reuters.rfa.omm.OMMMsg;

/**
 * MarketByOrderGenerator provides a simple algorithm to generate data for
 * Market By Order domain. It can be used to create MarketByOrderStreamItems.
 * 
 * <p>
 * <b>How to generate response data.</b>
 * </p>
 * 
 * Unique orders(key) are generated. The key is generated by keeping a running
 * total of entries added, which is used to set the value of the key. The price,
 * The side of the order, the quantity of the order, the market maker, and the
 * action (update, add, or delete) are chosen randomly during message updates to
 * generate more realistic data. The time of the order is the current time in
 * which response data be created.
 * 
 * @see MarketByOrderStreamItem
 */
public class MarketByOrderGenerator implements DataGenerator
{

    private List<OrderEntry> _orderEntries;
    private List<OrderEntry> _modifiedEntries;
    private int _id;
    private int _basePrice;

    // the maximum number of caches
    private int _maxEntries = 50;
    private Random _random;

    private int _deleteFactor = 40; // 40% chance of deleting a current entry
    // private int _addFactor = 40; // 40% chance of adding a current entry
    private int _updateFactor = 20; // 20% chance of updating a current entry

    /**
     * Represents a single order entry
     */
    public static class OrderEntry implements Cloneable
    {

        public byte action; // UPDATE, ADD, DELETE
        public String orderId;
        public int orderPrice;
        public short orderSide; // BID = 1, ASK = 2
        public long orderSize;
        public String makerId;
        public long quotim;

        public Object clone()
        {
            OrderEntry newEntry = new OrderEntry();
            newEntry.action = action;
            newEntry.orderId = orderId;
            newEntry.orderPrice = orderPrice;
            newEntry.orderSide = orderSide;
            newEntry.orderSize = orderSize;
            newEntry.quotim = quotim;
            newEntry.makerId = makerId;
            return newEntry;
        }
    }

    public MarketByOrderGenerator()
    {

        _orderEntries = new ArrayList<OrderEntry>(50);
        _modifiedEntries = new ArrayList<OrderEntry>(50);
        _random = new Random();
        _basePrice = (_random.nextInt(1000) + 1) * 100;
        _id = 0;

        for (int i = 0; i < 25; i++)
        {
            _orderEntries.add(getNewOrder());
        }
    }

    public DataStreamItem createStreamItem(ProvDomainMgr mgr, Token token, OMMMsg msg)
    {
        boolean encodeDataDef = CommandLine.booleanVariable("MARKET_BY_ORDER_encodeDataDef");

        MarketByOrderStreamItem streamItem = new MarketByOrderStreamItem(this, mgr, token, msg);
        streamItem.setEncodeDataDef(encodeDataDef);
        return streamItem;
    }

    public Object[] getInitialEntries()
    {
        return _orderEntries.toArray();
    }

    public Object[] getNextEntries()
    {
        return _modifiedEntries.toArray();
    }

    public void generateUpdatedEntries()
    {

        _modifiedEntries.clear();
        HashSet<String> newEntries = new HashSet<String>();

        for (int i = 0; i < 10; i++)
        {

            OrderEntry orderEntry = null;
            int action = _random.nextInt(100);

            if (_orderEntries.size() == 0 || action >= _deleteFactor + _updateFactor)
            {
                // Action Add
                if (_orderEntries.size() >= _maxEntries)
                    continue;

                orderEntry = getNewOrder();
                _orderEntries.add(orderEntry);

            }
            else if (action < _deleteFactor)
            {
                // Action Delete
                int index = _random.nextInt(_orderEntries.size());
                orderEntry = (OrderEntry)_orderEntries.get(index);

                if (newEntries.contains(orderEntry.orderId))
                    continue;

                orderEntry.action = OMMMapEntry.Action.DELETE;
                _orderEntries.remove(orderEntry);

            }
            else
            {
                // Action Update
                int index = _random.nextInt(_orderEntries.size());
                OrderEntry updateEntry = (OrderEntry)_orderEntries.get(index);

                if (newEntries.contains(updateEntry.orderId))
                    continue;

                updateOrder(updateEntry);
                orderEntry = (OrderEntry)updateEntry.clone();
                orderEntry.action = OMMMapEntry.Action.UPDATE;
            }

            newEntries.add(orderEntry.orderId);
            _modifiedEntries.add(orderEntry);
        }
    }

    private OrderEntry getNewOrder()
    {

        OrderEntry orderEntry = new OrderEntry();
        orderEntry.orderId = "XXX" + _id;
        orderEntry.makerId = "MMM" + _id;
        orderEntry.action = OMMMapEntry.Action.ADD;
        _id++;

        int percentChange = _random.nextInt(30);
        int change = ((_basePrice * percentChange) / 100);

        boolean isBid = _random.nextBoolean();

        if (isBid)
        {
            orderEntry.orderSide = 1;
            orderEntry.orderPrice = _basePrice - change;
        }
        else
        {
            orderEntry.orderSide = 2;
            orderEntry.orderPrice = _basePrice + change;
        }

        orderEntry.orderSize = _random.nextInt(1000000);

        long currentTime = System.currentTimeMillis();
        orderEntry.quotim = currentTime % (60 * 60 * 24 * 1000); // need midnight

        return orderEntry;
    }

    private void updateOrder(OrderEntry orderEntry)
    {

        int percentChange = _random.nextInt(10) + 1;
        int change = ((orderEntry.orderPrice * percentChange) / 100);

        if (orderEntry.orderSide == 1)
        {
            orderEntry.orderPrice = orderEntry.orderPrice - change;
        }
        else
        {
            orderEntry.orderPrice = orderEntry.orderPrice + change;
        }

        long currentTime = System.currentTimeMillis();
        orderEntry.quotim = currentTime % (60 * 60 * 24 * 1000); // need midnight
    }

}
